package com.yanh.seckill.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.yanh.jcommon.utils.R;
import com.yanh.jcommon.vo.seckill.SeckillOrder;
import com.yanh.jcommon.vo.seckill.SeckillSessionInfo;
import com.yanh.jcommon.vo.seckill.SeckillSessionWithSku;
import com.yanh.jcommon.vo.seckill.SeckillSku;
import com.yanh.seckill.feign.CouponFeignClient;
import com.yanh.seckill.feign.ProductFeignClient;
import com.yanh.seckill.service.SeckillService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class SeckillServiceImpl implements SeckillService {

    private final String SESSION_CACHE_PREFIX = "seckill:sessions:";
    private final String SKUSESSION_CHARE_PREFIX = "seckill:skussession:";
    private final String SESSIONSKU_CHARE_PREFIX = "seckill:sessionskus:";
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";
    private final String USER_KILL_PREFIX = "userkill:info:";

    private  final RedisTemplate<String, Object> redisTemplate;

    private final RedissonClient redissonClient;

    private final RabbitTemplate rabbitTemplate;

    private final CouponFeignClient couponFeignClient;

    private final ProductFeignClient productFeignClient;

    /**
     * 缓存秒杀活动信息
     * @param sessions
     */
    private void saveSessionWithSku(@NotNull List<SeckillSessionWithSku> sessions) {

        sessions.stream().forEach(session -> {
            List<Long> skuIdList=session.getSkuInfoList().stream().map(SeckillSku::getSkuId).collect(Collectors.toList());
            R info = productFeignClient.getSkuInfoList(skuIdList);
            if (info.getCode() == 0) {
                List<SeckillSku>skuInfoList = info.getData("skuInfoList",new TypeReference<List<SeckillSku>>(){});
                for (SeckillSku s: session.getSkuInfoList())
                {
                    var skuInfo=skuInfoList
                            .stream().filter(p->p.getSkuId().equals(s.getSkuId()))
                            .findFirst()  // 找到第一个符合条件的元素
                            .orElse(null);  // 如果没有找到返回null
                    if (skuInfo!=null){
                        s.setSkuTitle(skuInfo.getSkuTitle());
                        s.setSkuName(skuInfo.getSkuName());
                        s.setPrice(skuInfo.getPrice());
                        s.setSkuDefaultImg(skuInfo.getSkuDefaultImg());
                    }
                }
            }
        });
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String currentDate = sdf.format(date);

        var key=SESSION_CACHE_PREFIX+currentDate;
        //判断Redis中是否有该信息，如果没有才进行添加
        Boolean hasKey = redisTemplate.hasKey(key);
        //缓存秒杀活动信息
        if (!hasKey) {
            ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
            //24小时过期
            valueOperations.set(key, sessions,24,TimeUnit.HOURS);
        }
    }

    /**
     * 缓存秒杀活动场次商品对应信息
     * @param sessions
     */
    public void saveSessionKeySkuValueHash(List<SeckillSessionWithSku> sessions) {

        sessions.stream().forEach(session -> {
            List<Long> skuIdList=session.getSkuInfoList().stream().map(SeckillSku::getSkuId).collect(Collectors.toList());
            R info = productFeignClient.getSkuInfoList(skuIdList);
            if (info.getCode() == 0) {
                List<SeckillSku>skuInfoList = info.getData("skuInfoList",new TypeReference<List<SeckillSku>>(){});
                //key
                var key=SESSIONSKU_CHARE_PREFIX+session.getId();
                //准备活动场次hash操作，绑定hash
                BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(key);
                session.getSkuInfoList().stream().forEach(s -> {

                    String redisKey =  s.getSkuId().toString();
                    if (!operations.hasKey(redisKey)) {

                        var skuInfo=skuInfoList
                                .stream().filter(p->p.getSkuId().equals(s.getSkuId()))
                                .findFirst()  // 找到第一个符合条件的元素
                                .orElse(null);  // 如果没有找到返回null

                        if (skuInfo!=null){
                            //生成随机码
                            String token = UUID.randomUUID().toString().replace("-", "");
                            s.setSkuTitle(skuInfo.getSkuTitle());
                            s.setSkuName(skuInfo.getSkuName());
                            s.setPrice(skuInfo.getPrice());
                            s.setSkuDefaultImg(skuInfo.getSkuDefaultImg());
                            //设置商品的随机码（防止恶意攻击）
                            s.setRandomCode(token);
                            operations.put(redisKey,s);
                            //设置redis过期时间
                            redisTemplate.expire(key, 24, TimeUnit.HOURS);
                            //使用库存作为分布式Redisson信号量（限流）
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                            // 商品可以秒杀的数量作为信号量
                            semaphore.trySetPermits(s.getSeckillCount());

                            // 设置信号量的过期时间为1小时
                            semaphore.expire(24, TimeUnit.HOURS);
                        }
                    }
                });
            }
        });
    }

    /**
     * 缓存秒杀活动商品场次对应信息
     * @param sessions
     */
    public void saveSkuKeySessionValueHash(List<SeckillSessionWithSku> sessions) {

        List<Long>temp=new ArrayList<>();
        sessions.stream().forEach(session -> {
            List<Long> skuIdList=session.getSkuInfoList().stream().map(SeckillSku::getSkuId).collect(Collectors.toList());
            temp.addAll(skuIdList);
        });
        Set<Long> set = new HashSet<>(temp);
        var skuResult= new ArrayList<>(set);
        for (Long s: skuResult)
        {
            //key
            var key=SKUSESSION_CHARE_PREFIX+s.toString();
            //准备活动场次hash操作，绑定hash
            BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(key);
            for (SeckillSessionWithSku session: sessions) {

               var existSku=session.getSkuInfoList()
                       .stream().filter(p->p.getSkuId().equals(s)).findFirst()  // 找到第一个符合条件的元素
                       .orElse(null);  // 如果没有找到返回null
                if (existSku!=null){
                    SeckillSessionInfo seckillSessionInfo=new SeckillSessionInfo();
                    seckillSessionInfo.setId(session.getId());
                    seckillSessionInfo.setName(session.getName());
                    seckillSessionInfo.setEndTime(session.getEndTime());
                    seckillSessionInfo.setStartTime(session.getStartTime());
                    operations.put(session.getId().toString(),seckillSessionInfo);
                    //设置redis过期时间
                    redisTemplate.expire(key, 24, TimeUnit.HOURS);
                }
            }
        }
    }

    /**
     * 上架三天需要秒杀的商品
     */
    @Override
    public void uploadSeckillSkuLatest3Days() {
        //1、扫描最近三天的商品需要参加秒杀的活动
        R lates3DaySession = couponFeignClient.getLates3DaySession();
        if (lates3DaySession.getCode() == 0) {
            //场次商品关系
            List<SeckillSessionWithSku> sessionData = lates3DaySession.getData("data", new TypeReference<List<SeckillSessionWithSku>>() {
            });

            //1、缓存活动信息
            saveSessionWithSku(sessionData);

            //2、缓存秒杀活动场次商品对应信息
            saveSessionKeySkuValueHash(sessionData);

            //3、缓存秒杀活动商品场次对应信息
            saveSkuKeySessionValueHash(sessionData);
        }
    }

    /**
     * 查询秒杀活动商品信息
     * @return
     */
    @Override
    public List<SeckillSessionWithSku> getSeckillSessionSkuList() {

        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String currentDate = sdf.format(date);
        var key=SESSION_CACHE_PREFIX+currentDate;
        List<SeckillSessionWithSku> list = (List<SeckillSessionWithSku>) redisTemplate.opsForValue().get(key);
        return  list;
    }

    /**
     * 根据skuId查询商品是否参加秒杀活动
     * @param skuId
     * @return
     */
    @Override
    public SeckillSessionInfo getSkuSeckillSessionInfo(Long skuId) {
        var key=SKUSESSION_CHARE_PREFIX+skuId.toString();
        BoundHashOperations<String, String, SeckillSessionInfo> operations = redisTemplate.boundHashOps(key);
        var list=operations.values();
        if (list!=null){
         var session=list.stream().min(Comparator.comparing(SeckillSessionInfo::getStartTime)).get();
            return session;
        }
        return null;
    }

    /**
     * 当前商品进行秒杀（秒杀开始）
     * @param promotionId
     * @param skuId
     * @param randomCode
     * @param num
     * @return
     */
    @Override
    public String kill(Long promotionId, Long skuId, String randomCode, Integer num) throws InterruptedException {

        String orderSn="秒杀失败";

        long start = System.currentTimeMillis();

        ////获取当前用户的信息
        //MemberResponseVo user = LoginUserInterceptor.loginUser.get();

        //sku合法性校验
        var skukey=SESSIONSKU_CHARE_PREFIX+promotionId.toString();
        BoundHashOperations<String, String, SeckillSku> hashOps = redisTemplate.boundHashOps(skukey);
        var skuInfoValue = hashOps.get(skuId.toString());
        if (skuInfoValue==null) {
            return  orderSn;
        }
        //(场次合法性效验)
        var sessionkey=SKUSESSION_CHARE_PREFIX+skuId.toString();
        BoundHashOperations<String, String, SeckillSessionInfo> skuhashOps = redisTemplate.boundHashOps(sessionkey);
        SeckillSessionInfo sessionInfoValue = skuhashOps.get(promotionId.toString());
        if (sessionInfoValue==null) {
            return  orderSn;
        }
        long startTime = sessionInfoValue.getStartTime().getTime();
        long endTime = sessionInfoValue.getEndTime().getTime();
        long currentTime = System.currentTimeMillis();
        String redisRandomCode=skuInfoValue.getRandomCode();
        //判断当前这个秒杀请求是否在活动时间区间内(效验时间的合法性)/效验随机码
        if (currentTime >= startTime && currentTime <= endTime&&randomCode.equals(redisRandomCode)) {
            //验证购物数量是否合理和库存量是否充足
            Integer seckillLimit = skuInfoValue.getSeckillLimit();
            //获取信号量
            Integer count = (Integer)redisTemplate.opsForValue().get(SKU_STOCK_SEMAPHORE + randomCode);
//            Integer count = Integer.valueOf(seckillCount);
            //判断信号量是否大于0,并且买的数量不能超过库存
            if (count > 0 && count > num && num <= seckillLimit) {
                //4、验证是否已经买过了（幂等性处理）,userId-sessionId-skuId
                //SETNX 原子性处理
                // 创建Random实例
                Random random = new Random();
                // 生成0到1000之间的随机数
                int randomNum = random.nextInt(1000);
                String redisKey =USER_KILL_PREFIX+ randomNum + "_"+promotionId.toString()+"_" + skuId.toString();
                //设置自动过期(活动结束时间-当前时间)
                Long ttl = endTime - currentTime;
                Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                if (aBoolean) {
                    //占位成功说明从来没有买过,分布式锁(获取信号量-1)
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                    //TODO 秒杀成功，快速下单
                    boolean semaphoreCount = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                    //保证Redis中还有商品库存
                    if (semaphoreCount) {
                        //创建订单号和订单信息发送给MQ
                        String timeId = UUID.randomUUID().toString().replace("-", "");
                        SeckillOrder orderTo = new SeckillOrder();
                        orderTo.setOrderSn(timeId);
                        orderTo.setMemberId(1L);
                        orderTo.setNum(num);
                        orderTo.setSessionId(promotionId);
                        orderTo.setSkuId(skuId);
                        orderTo.setSeckillPrice(skuInfoValue.getSeckillPrice());
                        rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
                        orderSn =timeId;
                    }
                }
            }
        }
        long end = System.currentTimeMillis();
        log.info("耗时..." + (end - start));
        return  orderSn;
    }
}
